package dexter.core.client;

import java.util.Properties;

import dexter.core.common.FinishParams;
import dexter.core.common.FinishResult;
import dexter.core.common.InitParams;
import dexter.core.common.InitResult;
import dexter.exception.InvalidGroupException;
import dexter.exception.InvalidJobException;
import dexter.exception.InvalidResultException;
import dexter.exception.RemoteTestException;
import dexter.exception.TimeoutException;
import dexter.net.Producer;
import dexter.net.msg.common.RequestParams;
import dexter.testing.EvaluationValues;
import dexter.testing.GenericTest;
import dexter.testing.TestFactory;
import dexter.util.ByteWrapper;
import dexter.util.PublicIp;


public class LocalClient implements Client {
	
	private String publicIp = "";
	private Producer producer;
	private final int TIMEOUT = 100;
	
	public LocalClient(String host, int port) {
		publicIp = PublicIp.getPublicIp();
	    producer = new Producer(host,port);
	}

	/**
	 * Start an experiment in the coordinator server. 
	 * @param group id
	 * @return job identifier created by the coordinator
	 * @throw InvalidGroupException if the id is not validated by the coordinator
	 */
	private String startTest(String gid, String nodepath, String classname) throws ClassNotFoundException, InvalidGroupException, InvalidResultException, TimeoutException {
		
		byte[] classbytes = ByteWrapper.getClassBytes(nodepath, classname);
        if (classbytes==null || classbytes.length==0) {
        	System.out.println("ERROR LOADING : "+classname+" in "+nodepath+" path");
        	return null;
		}
		System.out.println("LOADED : "+classname+" ("+classbytes.length+" bytes)");
		
		
		RequestParams iparams = new InitParams(gid, classbytes, false);
		InitListener ilistener = new InitListener();
		
		producer.send(iparams, ilistener);

		int count = 0;
		while(!ilistener.hasResult() && count < TIMEOUT) {
			try { Thread.sleep(300); } catch (Exception e) {}
			count++;
		}
		
		if (count>=TIMEOUT) throw new TimeoutException("Timeout waiting for test initialization, is the coordinator server active?");
		
		InitResult result = ilistener.getResult();
		if (result==null) throw new InvalidResultException("Error in the response of the coordinator server, please try again later");
		
		boolean validated = result.isValidated();
		
		if (!validated) throw new InvalidGroupException("Invalidated group ("+gid+") Error, please check this param or register your group in the website");
		
		String job = result.getJob();
		
		return job;
		
	}

    /**
     * 
     * Finish an experiment in the coordinator server. 
     * @param jobId
     * @param serverClass
     * @param localResult
     * @return Serializable result
     * @throws TimeoutException
     * @throws InvalidResultException
     */
	private FinishResult finishTest(String groupId, String jobId, String nodepath, String classname, EvaluationValues localResult) throws ClassNotFoundException, TimeoutException, InvalidResultException {
		
		byte[] classbytes = ByteWrapper.getClassBytes(nodepath, classname);
        if (classbytes==null || classbytes.length==0) {
        	System.out.println("ERROR LOADING : "+classname+" in "+nodepath+" path");
        	return null;
		}
		System.out.println("LOADED : "+classname+" ("+classbytes.length+" bytes)");
		
		
		FinishListener flistener = new FinishListener();
		RequestParams fparams = new FinishParams(groupId, jobId, publicIp, localResult, classname, classbytes);
		producer.send(fparams, flistener);
		
		int count = 0;
		while(!flistener.hasResult() && count < TIMEOUT) {
			try { Thread.sleep(300); } catch (Exception e) {}
			count++;
		}
		
		if (count>=TIMEOUT) throw new TimeoutException("Timeout waiting for test finalitzation, is the coordinator server active?");
		
		FinishResult result = flistener.getResult();
		if (result==null) throw new InvalidResultException("Error in the response of the coordinator server, please try again later");
		
		return result;
		
	}
	
	/* (non-Javadoc)
	 * @see remote.client.Client#executeTest(java.util.Properties)
	 */
	public void executeTest(Properties props) throws RemoteTestException  {
		
		String groupId = props.getProperty("group");
		String classname = props.getProperty("node_classname");
		String nodepath = props.getProperty("node_path");
		
		String job = null;
		try {
           job = startTest(groupId, nodepath, classname);  
		} catch (Exception e) {
			throw new RemoteTestException("Remote error in the LOCAL mode execution", e);
		}
		
		props.setProperty("session_id", job);
		
		try {
			   EvaluationValues localResult = executeLocalTest(props);
			
		
	           FinishResult result = finishTest(groupId, job, nodepath, classname, localResult); 
	           if (!result.isOk()) throw new InvalidJobException("Invalidated job or group error, please try again later");
	           String expId = result.getExperimentId();
	           EvaluationValues finalValues = result.getRemoteResult();
	           
	           prettyPrintResults(expId, finalValues);
	    } catch (Exception e) {
				throw new RemoteTestException("Remote error in the LOCAL mode execution", e);
        }
		
	}

	
	private EvaluationValues executeLocalTest(Properties props) throws Exception {
		
		System.out.println("PREPARING LOCAL TEST...");		
		GenericTest tester = TestFactory.createTest(props);
		tester.before(props);
		System.out.println("STARTING TEST..., PLEASE WAIT FOR THE LOGS");
		EvaluationValues er = tester.test();
		System.out.println("FINISHING TEST...");
		tester.after();
		return er;
	}
	
	private void prettyPrintResults(String expId, EvaluationValues finalValues) {
		   System.out.println("Experiment ("+expId+") finished");
           System.out.println("Results :");
           System.out.println("Test Phase2  : "+finalValues.getResultValue(0)+" : "+finalValues.getResultInfo(0));
           System.out.println("Test PHase3  : "+finalValues.getResultValue(1)+" : "+finalValues.getResultInfo(1));
           System.out.println("Test PHase32 : "+finalValues.getResultValue(2)+" : "+finalValues.getResultInfo(2));
	}

}
